package wechat

import (
	"anban/utils"
	"encoding/xml"
	"fmt"
	"github.com/astaxie/beego"
	"sort"
	"strings"
	"time"
)

// 小程序纯签约
type MiniContractExtraData struct {
	MchId                  string `json:"mch_id"`                   // 微信支付分配的商户号
	SubMchId               string `json:"sub_mch_id"`               // 微信支付分配的子商户号
	Appid                  string `json:"appid"`                    // 服务商商户号绑定的appid
	PlanId                 string `json:"plan_id"`                  // 协议模板id
	ContractCode           string `json:"contract_code"`            // 商户侧的签约协议号，由商户生成，商户侧须唯一
	RequestSerial          int64  `json:"request_serial"`           // 商户请求签约时的序列号。序列号主要用于排序，不作为查询条件，纯数字。长度不超过12位
	ContractDisplayAccount string `json:"contract_display_account"` // 签约用户的名称，用于页面展示，不需要对值进行urlencode，参数值不支持UTF8非3字节编码的字符，例如表情符号，所以请勿传微信昵称到该字段
	NotifyUrl              string `json:"notify_url"`               // HTTP或者HTTPS开头的回调通知url ,不需要对值进行urlencode
	Sign                   string `json:"sign"`                     // 是对extraData内所有参数按照签名生成算法做签名，所有参数都是encode前的值参与签名
	Timestamp              string `json:"timestamp"`                // 系统当前时间戳(10位秒)
	Clientip               string `json:"clientip"`                 // 点分IP格式(客户端IP)
	Mobile                 string `json:"mobile"`                   // 用户手机号
	Openid                 string `json:"openid"`                   // 用户微信open ID
	Outerid                string `json:"outerid"`                  // 用户在商户侧的标识
}

type MiniContract struct {
	Appid     string                 `json:"appid"` // 固定值wxbd687630cd02ce1d
	ExtraData *MiniContractExtraData `json:"extraData"`
	Path      string                 `json:"path"`     // 固定值：pages/index/index
	Success   string                 `json:"success"`  // 接口调用成功的回调函数
	Fail      string                 `json:"fail"`     // 接口调用失败的回调函数
	Complete  string                 `json:"complete"` // 接口调用结束的回调函数
}

type MiniContractRes struct {
	XMLName             xml.Name `xml:"xml"`
	ReturnCode          string   `xml:"return_code"`           // SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
	ReturnMsg           string   `xml:"return_msg"`            // 返回信息，如非空，为错误原因
	ResultCode          string   `xml:"result_code"`           // 业务结果，SUCCESS/FAIL
	MchId               string   `xml:"mch_id"`                // 微信支付分配的商户号
	SubMchId            string   `xml:"sub_mch_id"`            // 微信支付分配的子商户号
	ContractCode        string   `xml:"contract_code"`         // 签约协议号
	PlanId              string   `xml:"plan_id"`               // 协议模板id
	Openid              string   `xml:"openid"`                // Appid下，用户的唯一标识
	SubOpenid           string   `xml:"sub_openid"`            // sub_appid下，用户的唯一标识
	Sign                string   `xml:"sign"`                  // 签名
	ChangeType          string   `xml:"change_type"`           // 有两个变更类型取值: ADD--签约 DELETE--解约
	OperateTime         string   `xml:"operate_time"`          // 操作时间
	ContractId          string   `xml:"contract_id"`           // 签约成功后，微信返回的委托代扣协议id
	ContractExpiredTime string   `xml:"contract_expired_time"` // 协议到期时间，当change_type为ADD时有返回
	RequestSerial       string   `xml:"request_serial"`        // 商户请求签约时的序列号，商户侧须唯一。序列号主要用于排序，不作为查询条件，纯数字,范围不能超过Int64的范围（9223372036854775807）。
}

type MiniContractRet struct {
	XMLName    xml.Name `xml:"xml"`
	ReturnCode string   `xml:"return_code"` // SUCCESS/FAIL SUCCESS表示商户接收通知成功并校验成功
	ReturnMsg  string   `xml:"return_msg"`  // 返回信息，如非空，为错误原因
}

func NewMiniContract(requestSerial int64, contractCode, contractDisplayAccount, clientIp, mobile, openId, outerId string) *MiniContract {
	appId := beego.AppConfig.String("wxd1e6cd0855bbdf9f")
	mchId := beego.AppConfig.String("mchid")
	subMchId := beego.AppConfig.String("submchid")
	planId := beego.AppConfig.String("miniplanid")
	notifyUrl := beego.AppConfig.String("contractnotifyurl")
	apiKey := beego.AppConfig.String("apikey")
	timestamp := fmt.Sprintf("%d", time.Now().Unix())
	extraData := &MiniContractExtraData{
		MchId:                  mchId,
		SubMchId:               subMchId,
		Appid:                  appId,
		PlanId:                 planId,
		ContractCode:           contractCode,
		RequestSerial:          requestSerial,
		ContractDisplayAccount: contractDisplayAccount,
		NotifyUrl:              notifyUrl,
		Sign:                   "",
		Timestamp:              timestamp,
		Clientip:               clientIp,
		Mobile:                 mobile,
		Openid:                 openId,
		Outerid:                outerId,
	}
	miniContract := MiniContract{
		Appid:     "wxbd687630cd02ce1d",
		ExtraData: extraData,
		Path:      "pages/index/index",
		Success:   "success",
		Fail:      "fail",
		Complete:  "complete",
	}
	extraDataMap := map[string]interface{}{
		"mch_id":                   extraData.MchId,
		"sub_mch_id":               extraData.SubMchId,
		"appid":                    extraData.Appid,
		"plan_id":                  extraData.PlanId,
		"contract_code":            extraData.ContractCode,
		"request_serial":           extraData.RequestSerial,
		"contract_display_account": extraData.ContractDisplayAccount,
		"notify_url":               extraData.NotifyUrl,
		"timestamp":                extraData.Timestamp,
		"clientip":                 extraData.Clientip,
		"mobile":                   extraData.Mobile,
		"openid":                   extraData.Openid,
		"outerid":                  extraData.Outerid,
	}
	keySlice := []string{}
	for k, _ := range extraDataMap {
		keySlice = append(keySlice, k)
	}
	sort.Strings(keySlice)
	paramStr := ""
	for _, v := range keySlice {
		if v == "request_serial" {
			paramStr += fmt.Sprintf("%s=%d&", v, extraDataMap[v])
		} else {
			paramStr += fmt.Sprintf("%s=%s&", v, extraDataMap[v])
		}
	}
	signStr := fmt.Sprintf("%skey=%s", paramStr, apiKey)
	sign := strings.ToUpper(utils.Encode(signStr))
	extraData.Sign = sign
	return &miniContract
}

// 小程序纯签约异步通知签名校验
func (m *MiniContractRes) CheckSign() bool {
	apiKey := beego.AppConfig.String("apikey")
	miniContractResMap := map[string]string{
		"return_code":           m.ReturnCode,
		"return_msg":            m.ReturnMsg,
		"result_code":           m.ResultCode,
		"mch_id":                m.MchId,
		"sub_mch_id":            m.SubMchId,
		"contract_code":         m.ContractCode,
		"plan_id":               m.PlanId,
		"openid":                m.Openid,
		"sub_openid":            m.SubOpenid,
		"change_type":           m.ChangeType,
		"operate_time":          m.OperateTime,
		"contract_id":           m.ContractId,
		"contract_expired_time": m.ContractExpiredTime,
		"request_serial":        m.RequestSerial,
	}
	keySlice := []string{}
	for k, _ := range miniContractResMap {
		keySlice = append(keySlice, k)
	}
	sort.Strings(keySlice)
	paramStr := ""
	for _, v := range keySlice {
		if len(miniContractResMap[v]) > 0 {
			paramStr += fmt.Sprintf("%s=%s&", v, miniContractResMap[v])
		}
	}
	signStr := fmt.Sprintf("%skey=%s", paramStr, apiKey)
	sign := strings.ToUpper(utils.Encode(signStr))
	if sign != m.Sign {
		return false
	}
	return true
}

func GetMiniContractRet(code, msg string) string {
	ret := MiniContractRet{
		ReturnCode: code,
		ReturnMsg:  msg,
	}
	content, _ := xml.Marshal(ret)
	return string(content)
}
